package com.gitee.wsl.func.predicate

import kotlin.reflect.KClass


    /**
     * A combinator that checks if **all** of the given `predicates` are satisfied.
     *
     *
     * By definition `allOf` is satisfied if the given `predicates` are empty.
     *
     * <pre>`Predicate<Integer> isGreaterThanOne = i -> i > 1;
     * Predicate<Integer> isGreaterThanTwo = i -> i > 2;
     * allOf().test(0);                                   // true
     * allOf(isGreaterThanOne, isGreaterThanTwo).test(3); // true
     * allOf(isGreaterThanOne, isGreaterThanTwo).test(2); // false
    `</pre> *
     *
     * @param predicates An array of predicates
     * @param <T>        closure over tested object types
     * @return A new `Predicate`
     * @throws NullPointerException if `predicates` is null
    </T> */
    fun <T> Predicate.Companion.allOf(vararg predicates: Predicate<T>): Predicate<T> {
        return Predicate<T> { t: T ->
            predicates.fold(true){ bool, pred ->
                bool && pred.test(t)
            }
        }
    }

    /**
     * A combinator that checks if **at least one** of the given `predicates` is satisfies.
     *
     * <pre>`Predicate<Integer> isGreaterThanOne = i -> i > 1;
     * Predicate<Integer> isGreaterThanTwo = i -> i > 2;
     * anyOf().test(0);                                   // false
     * anyOf(isGreaterThanOne, isGreaterThanTwo).test(3); // true
     * anyOf(isGreaterThanOne, isGreaterThanTwo).test(2); // true
     * anyOf(isGreaterThanOne, isGreaterThanTwo).test(1); // false
    `</pre> *
     *
     * @param predicates An array of predicates
     * @param <T>        closure over tested object types
     * @return A new `Predicate`
     * @throws NullPointerException if `predicates` is null
    </T> */
    fun <T> Predicate.Companion.anyOf(vararg predicates: Predicate<T>): Predicate<T> {
        return Predicate<T> { t: T ->
            predicates.any{ it.test(t) }
        }
    }

    /**
     * A combinator that checks if **one or more** elements of an `Iterable` satisfy the `predicate`.
     *
     * <pre>`Predicate<Integer> isGreaterThanOne = i -> i > 1;
     * Predicate<Iterable<Integer>> existsGreaterThanOne = exists(isGreaterThanOne);
     * existsGreaterThanOne.test(List.of(0, 1, 2)); // true
     * existsGreaterThanOne.test(List.of(0, 1));    // false
    `</pre> *
     *
     * @param predicate A `Predicate` that tests elements of type `T`
     * @param <T>        tested object type
     * @return A new `Predicate`
     * @throws NullPointerException if `predicate` is null
    </T> */
    fun <T> Predicate.Companion.exists(predicate: Predicate<T>): Predicate<Iterable<T>> {
        return Predicate<Iterable<T>> { iterable: Iterable<T> ->
            iterable.any { predicate.test(it) }
        }
    }

    /**
     * A combinator that checks if **all** elements of an `Iterable` satisfy the `predicate`.
     *
     * <pre>`Predicate<Integer> isGreaterThanOne = i -> i > 1;
     * Predicate<Iterable<Integer>> forAllGreaterThanOne = forAll(isGreaterThanOne);
     * forAllGreaterThanOne.test(List.of(0, 1, 2)); // false
     * forAllGreaterThanOne.test(List.of(2, 3, 4)); // true
    `</pre> *
     *
     * @param predicate A `Predicate` that tests elements of type `T`
     * @param <T>        tested object type
     * @return A new `Predicate`
     * @throws NullPointerException if `predicate` is null
    </T> */
    fun <T> Predicate.Companion.forAll(predicate: Predicate<in T>): Predicate<Iterable<T>> {
        return Predicate<Iterable<T>> { iterable: Iterable<T> ->
            iterable.all { predicate.test(it) }
        }
    }

    /**
     * Creates a `Predicate` that tests, if an object is instance of the specified `type`.
     *
     * <pre>`Predicate<Object> instanceOfNumber = instanceOf(Number.class);
     * instanceOfNumber.test(1);    // true
     * instanceOfNumber.test("1");  // false
    `</pre> *
     *
     * @param type A type
     * @param <T>  tested object type
     * @return A new `Predicate`
     * @throws NullPointerException if `type` is null
    </T> */
    // DEV-NOTE: We need Class<? extends T> instead of Class<T>, see {@link TryTest#shouldRecoverSuccessUsingCase()}

    fun <T : Any> Predicate.Companion.instanceOf(type: KClass<T>): Predicate<T> {
        return Predicate<T> { obj: T? -> obj != null && type == obj::class }
    }

    /**
     * Creates a `Predicate` that tests, if an object is equal to the specified `value` using
     * [Objects.equals] for comparison.
     *
     * <pre>`Predicate<Integer> isOne = is(1);
     * isOne.test(1); // true
     * isOne.test(2); // false
    `</pre> *
     *
     * @param value A value, may be null
     * @param <T>   tested object type
     * @return A new `Predicate`
    </T> */
    fun <T> Predicate.Companion.`is`(value: T): Predicate<T> {
        return Predicate { obj: T -> obj == value }
    }

    /**
     * Creates a `Predicate` that tests, if an object is equal to at least one of the specified `values`
     * using [Objects.equals] for comparison.
     *
     * <pre>`Predicate<Integer> isIn = isIn(1, 2, 3);
     * isIn.test(1); // true
     * isIn.test(0); // false
    `</pre> *
     *
     * @param values an array of values of type T
     * @param <T>    closure over tested object types
     * @return A new `Predicate`
     * @throws NullPointerException if `values` is null
    </T> */
    fun <T> Predicate.Companion.isIn(vararg values: T): Predicate<T> {
        return Predicate<T> { obj: T ->
           values.contains(obj)
        }
    }

    /**
     * Creates a `Predicate` that tests, if an object is not null
     *
     * <pre>`Predicate<Integer> isNotNull = isNotNull();
     * isNotNull.test(0);    // true
     * isNotNull.test(null); // false
    `</pre> *
     *
     * @param <T> tested object type
     * @return A new `Predicate`
    </T> */
    fun <T> Predicate.Companion.isNotNull(): Predicate<T> {
        return Predicate<T> { o: Any? -> o != null }
    }

    /**
     * Creates a `Predicate` that tests, if an object is null
     *
     * <pre>`Predicate<Integer> isNull = isNull();
     * isNull.test(null); // true
     * isNull.test(0);    // false
    `</pre> *
     *
     * @param <T> tested object type
     * @return A new `Predicate`
    </T> */
    fun <T> Predicate.Companion.isNull(): Predicate<T> {
        return Predicate<T> { o: Any? -> o == null }
    }

    /**
     * A combinator that checks if **none** of the given `predicates` is satisfied.
     *
     *
     * Naturally `noneOf` is satisfied if the given `predicates` are empty.
     *
     * <pre>`Predicate<Integer> isGreaterThanOne = i -> i > 1;
     * Predicate<Integer> isGreaterThanTwo = i -> i > 2;
     * noneOf().test(0);                                   // true
     * noneOf(isGreaterThanOne, isGreaterThanTwo).test(1); // true
     * noneOf(isGreaterThanOne, isGreaterThanTwo).test(2); // false
    `</pre> *
     *
     * @param predicates An array of predicates
     * @param <T>        closure over tested object types
     * @return A new `Predicate`
     * @throws NullPointerException if `predicates` is null
    </T> */
    fun <T> Predicate.Companion.noneOf(vararg predicates: Predicate<T>): Predicate<T> {
        return anyOf(*predicates).negate()
    }

    /**
     * Negates a given `Predicate`.
     *
     * <pre>`// negates a method reference
     * Predicate<String> isNotNull1 = not(Objects::isNull);
     * isNotNull1.test("");   // true
     * isNotNull1.test(null); // false
     *
     * // negates a predicate instance
     * Predicate<String> isNotNull2 = not(Predicates.isNull());
     * isNotNull2.test("");   // true
     * isNotNull2.test(null); // false
    `</pre> *
     *
     * @param predicate A `Predicate` that tests elements of type `T`
     * @param <T>       tested object type
     * @return A new `Predicate`
     * @throws NullPointerException if `predicate` is null
    </T> */
    fun <T> Predicate.Companion.not(predicate: Predicate<T>): Predicate<T> {
        return predicate.negate()
    }

/**
 * Returns a predicate that tests if two arguments are equal according
 * to [Objects.equals].
 *
 * @param <T> the type of arguments to the predicate
 * @param targetRef the object reference with which to compare for equality,
 * which may be `null`
 * @return a predicate that tests if two arguments are equal according
 * to [Objects.equals]
</T> */
fun <T> Predicate.Companion.isEqual(targetRef: Any): Predicate<T> {
    return  Predicate { `object`: T -> targetRef == `object` }
}

fun <T> Predicate.Companion.alwaysTrue(): Predicate<T> {
    return  Predicate { true }
}

fun <T> Predicate.Companion.alwaysFalse(): Predicate<T> {
    return  Predicate { false }
}